﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace g4
{
    /// <summary>
    /// 基于2D线段路径切割网格的操作类
    /// 功能：将2D多段线曲线插入到网格中，通过切割网格面和边生成与曲线重合的新边界
    /// 
    /// 假设条件：
    ///   - 网格顶点的x/y坐标对应需要使用的2D坐标（若不满足，需替换PointF函数）
    ///   - 曲线的所有线段完全位于网格的UV三角形内
    /// 
    /// 限制：
    ///   - 对近平行线段（处于输入环的epsilon范围内）不稳健，可能无法找到连通路径
    ///   - 对退化几何（如零面积面、零长度边）不稳健，强烈建议先调用Validate()或预处理网格
    /// </summary>
    public class MeshInsertUVPolyCurve
    {
        /// <summary>
        /// 目标网格（将被插入曲线切割的网格）
        /// </summary>
        public DMesh3 Mesh;

        /// <summary>
        /// 要插入的2D多段线曲线
        /// </summary>
        public PolyLine2d Curve;

        /// <summary>
        /// 标记曲线是否为闭合环（首尾顶点相连）
        /// </summary>
        public bool IsLoop;

        /// <summary>
        /// 用于获取顶点UV坐标的函数（默认返回顶点x/y坐标）
        /// 输入：顶点ID；输出：2D UV坐标
        /// </summary>
        public Func<int, Vector2d> PointF;

        /// <summary>
        /// 用于设置顶点UV坐标的函数（默认设置x/y坐标，z=0）
        /// 输入：顶点ID、2D UV坐标
        /// </summary>
        public Action<int, Vector2d> SetPointF;

        /// <summary>
        /// 是否启用切割跨度和环的计算（耗时操作，若不需要结果可关闭）
        /// </summary>
        public bool EnableCutSpansAndLoops = true;

        /// <summary>
        /// 是否使用三角形空间索引加速查询（对大网格建议开启）
        /// </summary>
        public bool UseTriSpatial = true;

        /// <summary>
        /// 空间容差：小于此距离的点/边被视为重合
        /// </summary>
        public double SpatialEpsilon = MathUtil.ZeroTolerance;

        // 操作结果

        /// <summary>
        /// 插入网格后，曲线顶点对应的网格顶点ID数组（按曲线顶点顺序）
        /// </summary>
        public int[] CurveVertices;

        /// <summary>
        /// 位于切割曲线上的边集合（无序）
        /// </summary>
        public HashSet<int> OnCutEdges;

        /// <summary>
        /// 插入网格后生成的边跨度列表（非闭合的切割路径）
        /// </summary>
        public List<EdgeSpan> Spans;

        /// <summary>
        /// 插入网格后生成的边环列表（闭合的切割路径）
        /// </summary>
        public List<EdgeLoop> Loops;


        /// <summary>
        /// 构造函数：初始化2D多段线插入操作
        /// </summary>
        /// <param name="mesh">目标网格</param>
        /// <param name="curve">2D多段线曲线</param>
        /// <param name="isLoop">是否为闭合环</param>
        public MeshInsertUVPolyCurve(DMesh3 mesh, PolyLine2d curve, bool isLoop = false)
        {
            Mesh = mesh;
            Curve = curve;
            IsLoop = isLoop;

            // 默认UV坐标映射：使用顶点x/y作为UV，z=0
            PointF = (vid) => { return Mesh.GetVertex(vid).xy; };
            SetPointF = (vid, pos) => { Mesh.SetVertex(vid, new Vector3d(pos.x, pos.y, 0)); };
        }

        /// <summary>
        /// 构造函数：从2D多边形初始化闭合环插入操作
        /// </summary>
        /// <param name="mesh">目标网格</param>
        /// <param name="loop">2D多边形（闭合环）</param>
        public MeshInsertUVPolyCurve(DMesh3 mesh, Polygon2d loop)
        {
            Mesh = mesh;
            Curve = new PolyLine2d(loop.Vertices);
            IsLoop = true;

            PointF = (vid) => { return Mesh.GetVertex(vid).xy; };
            SetPointF = (vid, pos) => { Mesh.SetVertex(vid, new Vector3d(pos.x, pos.y, 0)); };
        }

        /// <summary>
        /// 构造函数：从2D多段线路径初始化非闭合插入操作
        /// </summary>
        /// <param name="mesh">目标网格</param>
        /// <param name="path">2D多段线路径</param>
        public MeshInsertUVPolyCurve(DMesh3 mesh, PolyLine2d path)
        {
            Mesh = mesh;
            Curve = new PolyLine2d(path.Vertices);
            IsLoop = false;

            PointF = (vid) => { return Mesh.GetVertex(vid).xy; };
            SetPointF = (vid, pos) => { Mesh.SetVertex(vid, new Vector3d(pos.x, pos.y, 0)); };
        }


        /// <summary>
        /// 验证输入的有效性（检查退化几何）
        /// </summary>
        /// <param name="fDegenerateTol">退化容差</param>
        /// <returns>验证状态（Ok表示有效，其他值表示退化类型）</returns>
        public virtual ValidationStatus Validate(double fDegenerateTol = MathUtil.ZeroTolerancef)
        {
            double distSqrThresh = fDegenerateTol * fDegenerateTol;

            // 检查曲线是否有近退化线段（两点距离过小）
            int nStop = IsLoop ? Curve.VertexCount - 1 : Curve.VertexCount;
            for (int k = 0; k < nStop; ++k)
            {
                Vector2d v0 = Curve[k];
                Vector2d v1 = Curve[(k + 1) % Curve.VertexCount];
                if (v0.DistanceSquared(v1) < distSqrThresh)
                    return ValidationStatus.NearDenegerateInputGeometry;
            }

            // 检查网格是否有近退化边（两点距离过小）
            foreach (int eid in Mesh.EdgeIndices())
            {
                Index2i ev = Mesh.GetEdgeV(eid);
                if (PointF(ev.a).DistanceSquared(PointF(ev.b)) < distSqrThresh)
                    return ValidationStatus.NearDegenerateMeshEdges;
            }

            return ValidationStatus.Ok;
        }


        // 三角形空间索引（用于加速三角形包含查询）
        private TriangleBinsGrid2d triSpatial;

        /// <summary>
        /// 向空间索引添加三角形
        /// </summary>
        private void spatial_add_triangle(int tid)
        {
            if (triSpatial == null)
                return;
            Index3i tv = Mesh.GetTriangle(tid);
            Vector2d a = PointF(tv.a), b = PointF(tv.b), c = PointF(tv.c);
            triSpatial.InsertTriangleUnsafe(tid, ref a, ref b, ref c);
        }

        /// <summary>
        /// 向空间索引添加两个三角形
        /// </summary>
        private void spatial_add_triangles(int t0, int t1)
        {
            if (triSpatial == null)
                return;
            spatial_add_triangle(t0);
            if (t1 != DMesh3.InvalidID)
                spatial_add_triangle(t1);
        }

        /// <summary>
        /// 从空间索引移除三角形
        /// </summary>
        private void spatial_remove_triangle(int tid)
        {
            if (triSpatial == null)
                return;
            Index3i tv = Mesh.GetTriangle(tid);
            Vector2d a = PointF(tv.a), b = PointF(tv.b), c = PointF(tv.c);
            triSpatial.RemoveTriangleUnsafe(tid, ref a, ref b, ref c);
        }

        /// <summary>
        /// 从空间索引移除两个三角形
        /// </summary>
        private void spatial_remove_triangles(int t0, int t1)
        {
            if (triSpatial == null)
                return;
            spatial_remove_triangle(t0);
            if (t1 != DMesh3.InvalidID)
                spatial_remove_triangle(t1);
        }


        /// <summary>
        /// 插入曲线的顶点到网格中（依次为每个曲线顶点在网格中找到或创建对应顶点）
        /// </summary>
        /// <param name="meshVertsOnCurve">输出：与曲线顶点重合的原始网格顶点</param>
        private void insert_corners(HashSet<int> meshVertsOnCurve)
        {
            PrimalQuery2d query = new PrimalQuery2d(PointF);

            // 初始化三角形空间索引（若启用）
            if (UseTriSpatial)
            {
                int count = Mesh.TriangleCount + Curve.VertexCount;
                int bins = 32;
                if (count < 25) bins = 8;
                else if (count < 100) bins = 16;
                // 计算网格边界用于索引初始化
                AxisAlignedBox3d bounds3 = Mesh.CachedBounds;
                AxisAlignedBox2d bounds2 = new AxisAlignedBox2d(bounds3.Min.xy, bounds3.Max.xy);
                triSpatial = new TriangleBinsGrid2d(bounds2, bins);
                // 将所有三角形添加到空间索引
                foreach (int tid in Mesh.TriangleIndices())
                    spatial_add_triangle(tid);
            }

            // 检查点是否在三角形内的函数
            Func<int, Vector2d, bool> inTriangleF = (tid, pos) => {
                Index3i tv = Mesh.GetTriangle(tid);
                // 无符号查询（不关心三角形方向）
                int queryResult = query.ToTriangleUnsigned(pos, tv.a, tv.b, tv.c);
                return (queryResult == -1 || queryResult == 0); // -1=内部，0=边界
            };

            // 初始化曲线顶点数组
            CurveVertices = new int[Curve.VertexCount];
            for (int i = 0; i < Curve.VertexCount; ++i)
            {
                Vector2d vInsert = Curve[i];
                bool inserted = false;

                // 查找包含当前曲线顶点的三角形
                int containTid = DMesh3.InvalidID;
                if (triSpatial != null)
                {
                    containTid = triSpatial.FindContainingTriangle(vInsert, inTriangleF);
                }
                else
                {
                    // 遍历所有三角形查找包含关系（效率低，适合小网格）
                    foreach (int tid in Mesh.TriangleIndices())
                    {
                        Index3i tv = Mesh.GetTriangle(tid);
                        int queryResult = query.ToTriangleUnsigned(vInsert, tv.a, tv.b, tv.c);
                        if (queryResult == -1 || queryResult == 0)
                        {
                            containTid = tid;
                            break;
                        }
                    }
                }

                // 若找到包含三角形，插入顶点（面穿刺、边分割或复用顶点）
                if (containTid != DMesh3.InvalidID)
                {
                    Index3i tv = Mesh.GetTriangle(containTid);
                    // 计算重心坐标
                    Vector3d bary = MathUtil.BarycentricCoords(vInsert, PointF(tv.a), PointF(tv.b), PointF(tv.c));
                    // 插入顶点（返回新顶点ID或复用的现有顶点ID）
                    bool isExistingV;
                    int vid = insert_corner_from_bary(i, containTid, bary, 0.01, 100 * SpatialEpsilon, out isExistingV);
                    if (vid > 0)
                    {
                        CurveVertices[i] = vid;
                        if (isExistingV)
                            meshVertsOnCurve.Add(vid);
                        inserted = true;
                    }
                }

                // 若未找到包含三角形，尝试匹配网格边界上的现有顶点
                if (!inserted)
                {
                    foreach (int vid in Mesh.VertexIndices())
                    {
                        Vector2d v = PointF(vid);
                        if (vInsert.Distance(v) < SpatialEpsilon)
                        {
                            CurveVertices[i] = vid;
                            meshVertsOnCurve.Add(vid);
                            inserted = true;
                        }
                    }
                }

                // 若仍未插入，抛出异常（曲线顶点不在网格上或网格外）
                if (!inserted)
                {
                    throw new Exception($"MeshInsertUVPolyCurve.insert_corners: 曲线顶点 {i} 不在任何网格三角形内或边界上！");
                }
            }
        }


        /// <summary>
        /// 根据重心坐标在三角形内插入顶点（处理顶点在顶点、边或内部的情况）
        /// </summary>
        /// <param name="iCorner">曲线顶点索引</param>
        /// <param name="tid">三角形ID</param>
        /// <param name="baryCoords">重心坐标</param>
        /// <param name="baryTol">重心坐标容差（判断是否在顶点/边上）</param>
        /// <param name="spatialTol">空间容差</param>
        /// <param name="isExistingV">输出：是否复用了现有顶点</param>
        /// <returns>插入的顶点ID</returns>
        private int insert_corner_from_bary(int iCorner, int tid, Vector3d baryCoords,
            double baryTol, double spatialTol, out bool isExistingV)
        {
            isExistingV = false;
            Vector2d vInsert = Curve[iCorner];
            Index3i tv = Mesh.GetTriangle(tid);

            // 情况1：顶点在三角形的某个顶点上（复用现有顶点）
            int cornerV = -1;
            if (baryCoords.x > 1 - baryTol)
                cornerV = tv.a;
            else if (baryCoords.y > 1 - baryTol)
                cornerV = tv.b;
            else if (baryCoords.z > 1 - baryTol)
                cornerV = tv.c;
            if (cornerV != -1 && PointF(cornerV).Distance(vInsert) < spatialTol)
            {
                isExistingV = true;
                return cornerV;
            }

            // 情况2：顶点在三角形的边上（分割边）
            int splitEdge = -1;
            if (baryCoords.x < baryTol)
                splitEdge = 1;   // 边b-c
            else if (baryCoords.y < baryTol)
                splitEdge = 2;   // 边a-c
            else if (baryCoords.z < baryTol)
                splitEdge = 0;   // 边a-b
            if (splitEdge >= 0)
            {
                int eid = Mesh.GetTriEdge(tid, splitEdge);
                Index2i ev = Mesh.GetEdgeV(eid);
                Segment2d seg = new Segment2d(PointF(ev.a), PointF(ev.b));
                // 检查点是否在边上
                if (seg.DistanceSquared(vInsert) < spatialTol * spatialTol)
                {
                    Index2i et = Mesh.GetEdgeT(eid);
                    spatial_remove_triangles(et.a, et.b); // 从空间索引移除旧三角形

                    // 分割边
                    DMesh3.EdgeSplitInfo splitInfo;
                    MeshResult splitResult = Mesh.SplitEdge(eid, out splitInfo);
                    if (splitResult != MeshResult.Ok)
                        throw new Exception($"MeshInsertUVPolyCurve.insert_corner_from_bary: 边分割失败 - {splitResult}");
                    // 设置新顶点位置为曲线顶点位置
                    SetPointF(splitInfo.vNew, vInsert);

                    // 向空间索引添加新三角形
                    spatial_add_triangles(et.a, et.b);
                    spatial_add_triangles(splitInfo.eNewT2, splitInfo.eNewT3);

                    return splitInfo.vNew;
                }
            }

            // 情况3：顶点在三角形内部（穿刺面）
            spatial_remove_triangle(tid); // 从空间索引移除旧三角形

            DMesh3.PokeTriangleInfo pokeInfo;
            MeshResult result = Mesh.PokeTriangle(tid, baryCoords, out pokeInfo);
            if (result != MeshResult.Ok)
                throw new Exception($"MeshInsertUVPolyCurve.insert_corner_from_bary: 面穿刺失败 - {result}");

            // 设置新顶点位置为曲线顶点位置
            SetPointF(pokeInfo.new_vid, vInsert);

            // 向空间索引添加新三角形
            spatial_add_triangle(tid);
            spatial_add_triangle(pokeInfo.new_t1);
            spatial_add_triangle(pokeInfo.new_t2);

            return pokeInfo.new_vid;
        }


        /// <summary>
        /// 执行曲线插入操作的主方法
        /// </summary>
        /// <returns>操作是否成功</returns>
        public virtual bool Apply()
        {
            HashSet<int> onCurveVerts = new HashSet<int>(); // 与曲线顶点重合的原始网格顶点
            insert_corners(onCurveVerts); // 插入曲线顶点

            // 初始化辅助集合
            HashSet<int> zeroEdges = new HashSet<int>();       // 与曲线重合的边
            HashSet<int> zeroVertices = new HashSet<int>();    // 与曲线重合的顶点
            OnCutEdges = new HashSet<int>();                   // 切割曲线上的边

            HashSet<int> newEdges = new HashSet<int>();        // 新创建的边
            HashSet<int> newCutVertices = new HashSet<int>();  // 切割路径上的新顶点
            sbyte[] signs = new sbyte[2 * Mesh.MaxVertexID + 2 * Curve.VertexCount]; // 顶点在曲线哪一侧的标记

            HashSet<int> segTriangles = new HashSet<int>();    // 线段相关的三角形
            HashSet<int> segVertices = new HashSet<int>();     // 线段相关的顶点
            HashSet<int> segEdges = new HashSet<int>();        // 线段相关的边

            // 处理每条曲线线段，切割网格边以生成与线段重合的路径
            int N = IsLoop ? Curve.VertexCount : Curve.VertexCount - 1;
            for (int si = 0; si < N; ++si)
            {
                int i0 = si;
                int i1 = (si + 1) % Curve.VertexCount;
                Segment2d seg = new Segment2d(Curve[i0], Curve[i1]); // 当前线段

                int i0Vid = CurveVertices[i0]; // 线段起点对应的网格顶点
                int i1Vid = CurveVertices[i1]; // 线段终点对应的网格顶点

                // 若起点和终点已被边连接，直接标记该边为切割边
                int existingEdge = Mesh.FindEdge(i0Vid, i1Vid);
                if (existingEdge != DMesh3.InvalidID)
                {
                    add_cut_edge(existingEdge);
                    continue;
                }

                // 若启用空间索引，获取线段相关的三角形、顶点和边（加速后续处理）
                if (triSpatial != null)
                {
                    segTriangles.Clear(); segVertices.Clear(); segEdges.Clear();
                    AxisAlignedBox2d segBounds = new AxisAlignedBox2d(seg.P0); segBounds.Contain(seg.P1);
                    segBounds.Expand(MathUtil.ZeroTolerancef * 10);
                    triSpatial.FindTrianglesInRange(segBounds, segTriangles);
                    IndexUtil.TrianglesToVertices(Mesh, segTriangles, segVertices);
                    IndexUtil.TrianglesToEdges(Mesh, segTriangles, segEdges);
                }

                // 确定需要处理的顶点范围（全部顶点或线段相关顶点）
                int maxVid = Mesh.MaxVertexID;
                IEnumerable<int> vertices = Interval1i.Range(maxVid);
                if (triSpatial != null)
                    vertices = segVertices;

                // 并行计算每个顶点在当前线段的哪一侧（用于判断边是否被线段切割）
                if (signs.Length < maxVid)
                    signs = new sbyte[2 * maxVid];
                gParallel.ForEach(vertices, (vid) => {
                    if (Mesh.IsVertex(vid))
                    {
                        if (vid == i0Vid || vid == i1Vid)
                        {
                            signs[vid] = 0; // 顶点在线段上
                        }
                        else
                        {
                            Vector2d v2 = PointF(vid);
                            // 计算顶点在seg的哪一侧（-1=左，0=上，1=右），带空间容差
                            signs[vid] = (sbyte)seg.WhichSide(v2, SpatialEpsilon);
                        }
                    }
                    else
                        signs[vid] = sbyte.MaxValue; // 无效顶点
                });

                // 确定需要处理的边范围（全部边或线段相关边）
                int maxEid = Mesh.MaxEdgeID;
                newEdges.Clear();
                newCutVertices.Clear();
                newCutVertices.Add(i0Vid);
                newCutVertices.Add(i1Vid);

                // 切割与当前线段相交的边
                IEnumerable<int> edges = Interval1i.Range(maxEid);
                if (triSpatial != null)
                    edges = segEdges;
                foreach (int eid in edges)
                {
                    if (!Mesh.IsEdge(eid))
                        continue;
                    // 跳过新创建的边（避免重复处理）
                    if (eid >= maxEid || newEdges.Contains(eid))
                        continue;
                    // 跳过边界边（不处理边界）
                    if (Mesh.IsBoundaryEdge(eid))
                        continue;

                    Index2i ev = Mesh.GetEdgeV(eid);
                    int evaSign = signs[ev.a];
                    int evbSign = signs[ev.b];

                    // 检查边的顶点是否在当前线段上
                    bool evaInSegment = false;
                    if (evaSign == 0)
                        evaInSegment = onCurveVerts.Contains(ev.a) ||
                            Math.Abs(seg.Project(PointF(ev.a))) < (seg.Extent + SpatialEpsilon);
                    bool evbInSegment = false;
                    if (evbSign == 0)
                        evbInSegment = onCurveVerts.Contains(ev.b) ||
                            Math.Abs(seg.Project(PointF(ev.b))) < (seg.Extent + SpatialEpsilon);

                    // 处理顶点在 segment 上的情况
                    if (evaInSegment || evbInSegment)
                    {
                        if (evaInSegment && evbInSegment)
                        {
                            // 两边顶点都在 segment 上，该边属于切割路径
                            zeroEdges.Add(eid);
                            add_cut_edge(eid);
                            newCutVertices.Add(ev.a);
                            newCutVertices.Add(ev.b);
                        }
                        else
                        {
                            // 只有一个顶点在 segment 上，标记该顶点
                            int zvid = evaInSegment ? ev.a : ev.b;
                            zeroVertices.Add(zvid);
                            newCutVertices.Add(zvid);
                        }
                        continue;
                    }

                    // 边的两个顶点在 segment 同侧，无交点
                    if (evaSign * evbSign > 0)
                        continue;

                    // 计算边与 segment 的交点
                    Vector2d va = PointF(ev.a);
                    Vector2d vb = PointF(ev.b);
                    Segment2d edgeSeg = new Segment2d(va, vb);
                    IntrSegment2Segment2 intr = new IntrSegment2Segment2(seg, edgeSeg);
                    intr.Compute();

                    // 处理交点情况
                    if (intr.Type == IntersectionType.Segment)
                    {
                        // 边与 segment 重合（已在上方处理，此处为冗余检查）
                        zeroEdges.Add(eid);
                        newCutVertices.Add(ev.a);
                        newCutVertices.Add(ev.b);
                        add_cut_edge(eid);
                        continue;
                    }
                    else if (intr.Type != IntersectionType.Point)
                    {
                        continue; // 无交点
                    }

                    // 交点坐标
                    Vector2d x = intr.Point0;
                    // 检查交点是否在边上（避免端点外的交点）
                    bool xInSegment = Math.Abs(edgeSeg.Project(x)) < (edgeSeg.Extent - SpatialEpsilon);
                    if (!xInSegment)
                        continue;

                    // 分割边以插入交点
                    Index2i et = Mesh.GetEdgeT(eid);
                    spatial_remove_triangles(et.a, et.b); // 从空间索引移除旧三角形

                    DMesh3.EdgeSplitInfo splitInfo;
                    MeshResult splitResult = Mesh.SplitEdge(eid, out splitInfo);
                    if (splitResult != MeshResult.Ok)
                    {
                        throw new Exception($"MeshInsertUVSegment.Apply: 边分割失败 - {splitResult}");
                    }

                    // 设置新顶点位置为交点坐标
                    SetPointF(splitInfo.vNew, x);
                    newCutVertices.Add(splitInfo.vNew);

                    // 记录新创建的边
                    newEdges.Add(splitInfo.eNewBN);
                    newEdges.Add(splitInfo.eNewCN);

                    // 向空间索引添加新三角形
                    spatial_add_triangles(et.a, et.b);
                    spatial_add_triangles(splitInfo.eNewT2, splitInfo.eNewT3);

                    // 标记新边是否属于切割路径
                    Index2i ecn = Mesh.GetEdgeV(splitInfo.eNewCN);
                    if (newCutVertices.Contains(ecn.a) && newCutVertices.Contains(ecn.b))
                        add_cut_edge(splitInfo.eNewCN);

                    if (splitInfo.eNewDN != DMesh3.InvalidID)
                    {
                        newEdges.Add(splitInfo.eNewDN);
                        Index2i edn = Mesh.GetEdgeV(splitInfo.eNewDN);
                        if (newCutVertices.Contains(edn.a) && newCutVertices.Contains(edn.b))
                            add_cut_edge(splitInfo.eNewDN);
                    }
                }
            }

            // 提取切割路径（环和跨度）
            if (EnableCutSpansAndLoops)
                find_cut_paths(OnCutEdges);

            return true;
        }


        /// <summary>
        /// 将边添加到切割边集合（集中处理便于调试）
        /// </summary>
        private void add_cut_edge(int eid)
        {
            OnCutEdges.Add(eid);
        }


        /// <summary>
        /// 简化插入后的网格：折叠切割路径上不必要的边和顶点
        /// （减少冗余几何，保留曲线顶点的关键位置）
        /// </summary>
        public void Simplify()
        {
            for (int k = 0; k < Loops.Count; ++k)
            {
                EdgeLoop newLoop = simplify(Loops[k]);
                Loops[k] = newLoop;
            }
        }

        /// <summary>
        /// 简化单个边环：折叠非关键边，保留曲线顶点
        /// </summary>
        private EdgeLoop simplify(EdgeLoop loop)
        {
            HashSet<int> curveVerts = new HashSet<int>(CurveVertices); // 曲线顶点集合
            List<int> remainingEdges = new List<int>(); // 简化后保留的边

            foreach (int eid in loop.Edges)
            {
                Index2i ev = Mesh.GetEdgeV(eid);

                // 若边连接两个曲线顶点，保留该边
                if (curveVerts.Contains(ev.a) && curveVerts.Contains(ev.b))
                {
                    remainingEdges.Add(eid);
                    continue;
                }

                // 确定需要保留的顶点（曲线顶点优先）
                int keep = ev.a, discard = ev.b;
                Vector3d setTo = Vector3d.Zero;
                if (curveVerts.Contains(ev.b))
                {
                    keep = ev.b;
                    discard = ev.a;
                    setTo = Mesh.GetVertex(ev.b);
                }
                else if (curveVerts.Contains(ev.a))
                {
                    setTo = Mesh.GetVertex(ev.a);
                }
                else
                {
                    // 非曲线顶点，取中点
                    setTo = 0.5 * (Mesh.GetVertex(ev.a) + Mesh.GetVertex(ev.b));
                }

                // 检查折叠是否会导致法向量翻转（避免几何失真）
                if (MeshUtil.CheckIfCollapseCreatesFlip(Mesh, eid, setTo))
                {
                    remainingEdges.Add(eid);
                    continue;
                }

                // 检查折叠是否会破坏切割路径（避免环断裂）
                Index4i einfo = Mesh.GetEdge(eid);
                int c = IndexUtil.find_tri_other_vtx(keep, discard, Mesh.GetTriangle(einfo.c));
                int d = IndexUtil.find_tri_other_vtx(keep, discard, Mesh.GetTriangle(einfo.d));
                int ec = Mesh.FindEdge(discard, c);
                int ed = Mesh.FindEdge(discard, d);
                if (OnCutEdges.Contains(ec) || OnCutEdges.Contains(ed))
                {
                    remainingEdges.Add(eid);
                    continue;
                }

                // 执行边折叠
                DMesh3.EdgeCollapseInfo collapse;
                MeshResult result = Mesh.CollapseEdge(keep, discard, out collapse);
                if (result == MeshResult.Ok)
                {
                    Mesh.SetVertex(collapse.vKept, setTo); // 更新保留顶点位置
                    OnCutEdges.Remove(collapse.eCollapsed); // 移除被折叠的边
                }
                else
                {
                    remainingEdges.Add(eid); // 折叠失败，保留原边
                }
            }

            // 从保留的边重建环
            return EdgeLoop.FromEdges(Mesh, remainingEdges);
        }


        /// <summary>
        /// 从切割边集合中提取切割路径（环和跨度）
        /// </summary>
        private void find_cut_paths(HashSet<int> cutEdges)
        {
            Spans = new List<EdgeSpan>();
            Loops = new List<EdgeLoop>();

            HashSet<int> remaining = new HashSet<int>(cutEdges); // 未处理的切割边
            while (remaining.Count > 0)
            {
                int startEdge = remaining.First();
                remaining.Remove(startEdge);
                Index2i startEdgeV = Mesh.GetEdgeV(startEdge);

                // 向前遍历边路径
                bool isLoop;
                List<int> forwardSpan = walk_edge_span_forward(Mesh, startEdge, startEdgeV.a, remaining, out isLoop);

                // 若不是环，尝试向后遍历并合并路径
                if (!isLoop)
                {
                    List<int> backwardSpan = walk_edge_span_forward(Mesh, startEdge, startEdgeV.b, remaining, out isLoop);
                    if (isLoop)
                        throw new Exception("find_cut_paths: 遍历逻辑错误，向后遍历不应形成环！");
                    if (backwardSpan.Count > 1)
                    {
                        // 合并前后路径
                        backwardSpan.Reverse();
                        backwardSpan.RemoveAt(backwardSpan.Count - 1);
                        backwardSpan.AddRange(forwardSpan);
                        // 检查是否形成环
                        Index2i startEv = Mesh.GetEdgeV(backwardSpan[0]);
                        Index2i endEv = Mesh.GetEdgeV(backwardSpan[backwardSpan.Count - 1]);
                        isLoop = backwardSpan.Count > 2 &&
                            IndexUtil.find_shared_edge_v(ref startEv, ref endEv) != DMesh3.InvalidID;
                        forwardSpan = backwardSpan;
                    }
                }

                // 存储结果（环或跨度）
                if (isLoop)
                {
                    EdgeLoop loop = EdgeLoop.FromEdges(Mesh, forwardSpan);
                    Util.gDevAssert(loop.CheckValidity());
                    Loops.Add(loop);
                }
                else
                {
                    EdgeSpan span = EdgeSpan.FromEdges(Mesh, forwardSpan);
                    Util.gDevAssert(span.CheckValidity());
                    Spans.Add(span);
                }
            }
        }


        /// <summary>
        /// 从起始边和起始顶点向前遍历，构建边路径
        /// </summary>
        private static List<int> walk_edge_span_forward(DMesh3 mesh, int startEdge, int startPivotV,
            HashSet<int> edgeSet, out bool isClosedLoop)
        {
            isClosedLoop = false;
            List<int> edgeSpan = new List<int> { startEdge };

            int curPivotV = startPivotV;
            int stopPivotV = IndexUtil.find_edge_other_v(mesh.GetEdgeV(startEdge), startPivotV);
            Util.gDevAssert(stopPivotV != DMesh3.InvalidID);

            bool done = false;
            while (!done)
            {
                // 查找与当前顶点连接且在切割边集合中的边
                int nextEdge = -1;
                foreach (int nbrEdge in mesh.VtxEdgesItr(curPivotV))
                {
                    if (edgeSet.Contains(nbrEdge))
                    {
                        nextEdge = nbrEdge;
                        break;
                    }
                }

                // 未找到下一条边，路径结束
                if (nextEdge == -1)
                {
                    done = true;
                    break;
                }

                // 确定下一个顶点（当前边的另一端）
                Index2i nextEdgeV = mesh.GetEdgeV(nextEdge);
                curPivotV = (nextEdgeV.a == curPivotV) ? nextEdgeV.b : nextEdgeV.a;

                // 添加边到路径并从集合中移除
                edgeSpan.Add(nextEdge);
                edgeSet.Remove(nextEdge);

                // 检查是否形成环（回到起点）
                if (curPivotV == stopPivotV)
                {
                    done = true;
                    isClosedLoop = true;
                }
            }

            return edgeSpan;
        }
    }
}